package org.hrqing.chess.service;

import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.hrqing.chess.config.props.SystemConfigProps;
import org.hrqing.chess.dao.LogDao;
import org.hrqing.chess.dao.LogPieceDao;
import org.hrqing.chess.entity.Log;
import org.hrqing.chess.entity.LogPiece;
import org.hrqing.chess.frame.CenterBackgroundPanel;
import org.hrqing.chess.frame.ChessLabel;
import org.hrqing.chess.util.JSON;
import org.hrqing.chess.util.NumberUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.swing.*;
import java.awt.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentMap;

/**
 * @author Hrqing
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LogService {

    @Getter
    private static List<String> steps = new ArrayList<>();

    private final LogDao logDao;

    private final LogPieceDao logPieceDao;

    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    public static class LogInfo {
        /**
         * 游戏记录id
         */
        private String gameId;
        /**
         * 游戏记录名称
         */
        private String name;
        /**
         * 执行人
         */
        private ChessLabel.Color executing;
        /**
         * 棋盘方向
         */
        private ChessLabel.Color color;
        /**
         * 难度
         */
        private SystemConfigProps.Depth depth;
        /**
         * 执行时间
         */
        private CenterBackgroundPanel.ExecuteTime executeTime;
        /**
         * 步骤
         */
        private List<String> steps;
        /**
         * 棋子
         */
        private List<ChessLabel.Piece> pieces;
    }

    public List<Log> findAllLog() {
        return logDao.findCreatedTimeDesc();
    }

    public LogInfo findTopLog() {
        Optional<Log> logOptional = logDao.findTopCreatedTimeDesc();
        if (logOptional.isEmpty()) {
            return null;
        }
        Log log = logOptional.get();
        LogInfo result = new LogInfo();
        result.setGameId(log.getId());
        result.setName(log.getName() + "_" + log.getRemark());
        result.setExecuting(log.getExecuting());
        result.setColor(log.getColor());
        result.setDepth(log.getDepth());
        result.setExecuteTime(new CenterBackgroundPanel.ExecuteTime(log.getStart(), log.getRed(), log.getBlack(), log.getDiffer()));
        String step = log.getStep();
        if (step != null) {
            result.setSteps(Arrays.asList(step.split(",")));
        }

        List<LogPiece> logPieces = logPieceDao.findByLogId(log.getId());
        List<ChessLabel.Piece> pieces = new ArrayList<>();
        for (LogPiece logPiece : logPieces) {
            ChessLabel.Piece piece = new ChessLabel.Piece();
            piece.setColor(logPiece.getColor());
            piece.setPurpose(logPiece.getPurpose());
            piece.setX(logPiece.getX());
            piece.setY(logPiece.getY());
            pieces.add(piece);
        }
        result.setPieces(pieces);
        return result;
    }

    public List<LogPiece> findLogPieceByLogId(String logId) {
        return logPieceDao.findByLogId(logId);
    }

    @Transactional
    public void save(LogInfo logInfo) {
        Log log = new Log();
        log.setId(logInfo.gameId);
        log.setName(logInfo.name.split("_")[0]);
        log.setRemark(logInfo.name.split("_")[1]);
        log.setExecuting(logInfo.executing);
        log.setColor(logInfo.color);
        log.setDepth(logInfo.depth);
        log.setStart(logInfo.getExecuteTime().getStart());
        log.setRed(logInfo.getExecuteTime().getRed());
        log.setBlack(logInfo.getExecuteTime().getBlack());
        log.setDiffer(logInfo.getExecuteTime().getDiffer());
        log.setCreatedTime(LocalDateTime.now());
        List<String> steps = logInfo.getSteps();
        if (steps != null && !steps.isEmpty()) {
            log.setStep(String.join(",", steps));
        }
        logDao.save(log);

        List<LogPiece> logPieces = logPieceDao.findByLogId(log.getId());
        if (logPieces != null && !logPieces.isEmpty()) {
            logPieceDao.deleteAll(logPieces.stream().map(LogPiece::getId).toList());
        }
        List<ChessLabel.Piece> pieces = logInfo.getPieces();
        for (ChessLabel.Piece piece : pieces) {
            LogPiece logPiece = new LogPiece();
            logPiece.setId(UUID.randomUUID().toString());
            logPiece.setLogId(log.getId());
            logPiece.setColor(piece.getColor());
            logPiece.setPurpose(piece.getPurpose());
            logPiece.setSign(piece.getSign());
            logPiece.setX(piece.getX());
            logPiece.setY(piece.getY());
            logPieceDao.save(logPiece);
        }
    }

    @Transactional
    public void log(CenterBackgroundPanel panel) {
        LogInfo logInfo = new LogInfo();
        logInfo.setGameId(panel.getGameId());
        logInfo.setName(panel.getName());
        logInfo.setColor(panel.getColor());
        logInfo.setDepth(panel.getDepth());
        logInfo.setExecuting(panel.getExecuting());
        logInfo.setExecuteTime(panel.getExecuteTime());
        logInfo.setPieces(pieces(panel));
        save(logInfo);
    }

    @Transactional
    public void log(CenterBackgroundPanel panel, ChessLabel selected, int mx, int my) {
        String[] split = selected.getId().split("-");

        int selectedX = Integer.parseInt(split[0]);
        int selectedY = Integer.parseInt(split[1]);

        List<ChessLabel> chess = Arrays.stream(panel.getComponents())
                .map(mapper -> (ChessLabel) mapper).toList();

        Optional<ChessLabel> first = chess.stream().filter(filter -> filter.getColor().equals(selected.getColor()))
                .filter(filter -> filter.getPurpose().equals(ChessLabel.Purpose.KING))
                .findFirst();
        int y = 10;
        if (first.isPresent()) {
            y = Integer.parseInt(first.get().getId().split("-")[1]);
        }

        List<ChessLabel> list = new ArrayList<>(chess.stream().filter(filter -> filter.getColor().equals(selected.getColor()))
                .filter(filter -> filter.getPurpose().equals(selected.getPurpose()))
                .filter(filter -> filter.getId().split("-")[0].equals(Integer.toString(selectedX)))
                .toList());

        Map<ChessLabel.Color, String> simple = selected.getPurpose().getSimple();
        String prefix = "";
        if (y >= 6) {
            list.sort((o1, o2) -> {
                int y1 = Integer.parseInt(o1.getId().split("-")[1]);
                int y2 = Integer.parseInt(o2.getId().split("-")[1]);
                return Integer.compare(y1, y2);
            });
        } else {
            list.sort((o1, o2) -> {
                int y1 = Integer.parseInt(o1.getId().split("-")[1]);
                int y2 = Integer.parseInt(o2.getId().split("-")[1]);
                return Integer.compare(y2, y1);
            });
        }
        if (list.size() == 2) {
            int contains = contains(list, selected);
            if (contains == 0) {
                prefix = "前";
            } else if (contains == 1) {
                prefix = "后";
            }
        } else if (list.size() > 2) {
            int contains = contains(list, selected);
            prefix = (y >= 6) ? NumberUtils.format(contains + 1) : (contains + 1 + "");
        }

        String step = "";
        step += (y >= 6) ? (simple.get(selected.getColor()) + NumberUtils.format(10 - selectedX)) : simple.get(selected.getColor()) + selectedX;

        String target = "";
        switch (selected.getPurpose()) {
            case ChessLabel.Purpose.KNIGHT, ChessLabel.Purpose.BISHOP, ChessLabel.Purpose.GUARD -> {
                target += (y >= 6) ? (selectedY > my ? "进" : "退") : (selectedY > my ? "退" : "进");
                target += (y >= 6) ? (NumberUtils.format(10 - mx)) : (mx);
            }
            default -> {
                if (selectedY == my) {
                    target += "平";
                    target += (y >= 6) ? (NumberUtils.format(10 - mx)) : (mx);
                } else {
                    target += (y >= 6) ? (selectedY > my ? "进" : "退") : (selectedY > my ? "退" : "进");
                    target += (y >= 6) ? (NumberUtils.format(absolute(selectedY - my))) : (absolute(selectedY - my));
                }
            }
        }
        LogService.getSteps().add(prefix + step + target);

        LogInfo logInfo = new LogInfo();
        logInfo.setGameId(panel.getGameId());
        logInfo.setName(panel.getName());
        logInfo.setColor(panel.getColor());
        logInfo.setDepth(panel.getDepth());
        logInfo.setExecuting(panel.getExecuting());
        logInfo.setExecuteTime(panel.getExecuteTime());
        logInfo.setSteps(LogService.getSteps());
        List<ChessLabel.Piece> pieces = pieces(panel, selected, mx, my);
        logInfo.setPieces(pieces);
        save(logInfo);
    }

    private List<ChessLabel.Piece> pieces(JPanel panel) {
        List<ChessLabel.Piece> pieces = new ArrayList<>();
        for (Component component : panel.getComponents()) {
            if (component instanceof ChessLabel chess) {
                String[] split = chess.getId().split("-");
                pieces.add(new ChessLabel.Piece(chess.getColor(),
                        chess.getPurpose(),
                        chess.getSign(),
                        Integer.parseInt(split[0]),
                        Integer.parseInt(split[1])));
            }
        }
        return pieces;
    }

    private List<ChessLabel.Piece> pieces(JPanel panel, ChessLabel selected, int mx, int my) {
        List<ChessLabel.Piece> pieces = pieces(panel);
        pieces.removeIf(predicate -> predicate.getId().equals(selected.getId().replace("-clone", "")));
        pieces.add(new ChessLabel.Piece(selected.getColor(), selected.getPurpose(), selected.getSign(), mx, my));
        return pieces;
    }

    private static int contains(List<ChessLabel> values, ChessLabel value) {
        for (int i = 0; i < values.size(); i++) {
            if(values.get(i).getId().equals(value.getId().replace("-clone", ""))) {
                return i;
            }
        }
        return -1;
    }

    private static int absolute(int value) {
        return value < 0 ? value * -1 : value;
    }

    public static void loadLogInfo(CenterBackgroundPanel panel, LogService.LogInfo logInfo) {
        panel.setGameId(logInfo.getGameId());
        panel.setName(logInfo.getName());
        panel.setColor(logInfo.getColor());
        panel.setExecuting(logInfo.getExecuting());
        panel.setDepth(logInfo.getDepth());
        CenterBackgroundPanel.ExecuteTime executeTime = panel.getExecuteTime();
        long currentTimeMillis = System.currentTimeMillis();
        executeTime.setDiffer(executeTime.getDiffer() + (currentTimeMillis - executeTime.getStart()));
        executeTime.setStart(currentTimeMillis);
        if (logInfo.getExecuting().equals(ChessLabel.Color.RED)) {
            executeTime.setRed(System.currentTimeMillis());
        }else if (logInfo.getExecuting().equals(ChessLabel.Color.BLACK)) {
            executeTime.setBlack(System.currentTimeMillis());
        }
        panel.setExecuteTime(executeTime);

        if (logInfo.getSteps() != null) {
            LogService.getSteps().addAll(logInfo.getSteps());
        }

        for (ChessLabel.Piece piece : logInfo.getPieces()) {
            ChessLabel.Piece pieced = ChessLabel.piece(piece.getColor(), piece.getPurpose());
            if (pieced != null) {
                piece.setImage(pieced.getImage());
                piece.setTop(pieced.getTop());
                piece.setLeft(pieced.getLeft());
            }
        }

        List<ChessLabel.Piece> list = logInfo.getPieces().stream().filter(filter -> filter.getImage() != null && !filter.getImage().trim().isEmpty()).toList();
        ChessLabel.restore(panel, list);
        ChessLabel.selected(panel, panel.getColor());
    }

    @Transactional
    public void updateCreatedTime(String id, LocalDateTime date) {
        Optional<Log> optionalLog = logDao.findById(id);
        if (optionalLog.isPresent()) {
            Log log = optionalLog.get();
            log.setCreatedTime(date);
            logDao.save(log);
        }
    }

    @Transactional
    public void deleteLog(String id) {
        Optional<Log> optionalLog = logDao.findById(id);
        if (optionalLog.isPresent()) {
            Log log = optionalLog.get();
            List<LogPiece> logPieces = logPieceDao.findByLogId(log.getId());
            if (!logPieces.isEmpty()) {
                logPieceDao.deleteAll(logPieces.stream().map(LogPiece::getId).toList());
            }
            logDao.delete(id);
        }
    }

}
